<!--
SporeSpace - An open-source JavaScript implementation of Maxis's Spore Space prototype
Copyright (C) 2010 LlamaSlayers

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
--><!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-type" content="text/html;charset=UTF-8" /><title>SporeSpace</title><link href="sporespace-icon.ico" rel="shortcut icon" type="image/x-icon"/><style type="text/css">/*<![CDATA[*/
html {
	text-align: center;
	background: #111;
}
body {
	font: 1em sans-serif;
	margin: 0 auto .5em;
	width: 512px;
	background: #000;
	border: 1px solid #070707;
	-moz-border-radius: 0 0 1em 1em;
	-webkit-border-radius: 0 0 1em 1em;
	border-radius: 0 0 1em 1em;
	color: #fff;
}
a, :visited {
	color: #88f;
	text-decoration: none;
}
a:hover, a:focus, a:active {
	text-decoration: underline;
}
h1 {
	margin: 0;
	padding: 0;
}
#copyright {
	color: #aaa;
}
input {
	-moz-border-radius: 5px;
	-webkit-border-radius: 5px;
	border-radius: 5px;
	background: #777;
	border: 1px solid #333;
	font-size: 1em;
	margin: 1px;
}
input:hover {
	background: #eee;
}
/*]]>*/</style></head><body><h1><img src="sporespace-header.png" alt="SporeSpace" title="SporeSpace"/></h1><a style="position: absolute; top: 1em; right: 1em; color: #f7f7f7;" href="http://llamaslayers.net/daily-llama/sporespace">What the heck is<br/> a SporeSpace?</a><div style="position: relative; width: 512px; left: 50%; margin-left: -256px;"><div id="hudCenter" style="position: absolute; color: #fff; font-size: 2em; height: 384px; text-align: center; display: none; width: 512px; padding-top: 128px; z-index: 5; background-color: #000; background-color: rgba( 0, 0, 0, .5 );"></div><div id="hudPlanetLoading" style="position: absolute; color: #fff; font-size: 2em; height: 384px; text-align: center; display: none; width: 512px; padding-top: 128px; z-index: 5; background-color: #000; background-color: rgba( 0, 0, 0, .5 );">Loading Planet&hellip;</div><div id="controls" style="position: absolute; color: #fff; font-size: 2em; height: 384px; text-align: center; width: 512px; padding-top: 128px; z-index: 5; background-color: #000; background-color: rgba( 0, 0, 0, .5 );"><strong>Controls:</strong><br/><small>Click on stars and planets to go to them.<br/>Scroll up and down to zoom in and out.<br/><a href="javascript:void(document.getElementById('controls').style.display = 'none')">Click here to close this tutorial.</a></small></div></div><canvas id="c" width="512" height="512"></canvas><canvas id="tmpC" width="512" height="512" style="display: none;"></canvas><canvas id="creatureImg" width="310" height="310" style="display: none;"></canvas>
<br/>
<strong id="location">Loading...</strong>
<br/>
<span id="badges">0</span>/7 Badges


<br/>
<small><span id="fps">0</span> FPS</small>
<br/>
<input type="button" value="Save" onclick="saveState()"/><input type="button" value="Reset" onclick="resetState()"/><input type="button" value="Mark Visited Stars" onclick="showVisited=!showVisited"/>
<div id="cheat" style="display: none;"><input type="button" value="All binary stars" onclick="for(var i=0;i<starmap.length;i++)if(starmap[i][4]<0)starmap[i][4]=rand(0,3)"/><input type="button" value="3D glasses mode" onclick="threeDGlasses=!threeDGlasses"/><input type="button" value="Motion Blur" onclick="ctx.globalAlpha=ctx.globalAlpha==.5?1:.5"/><input type="button" value="Return Ticket" onclick="state=0;justFoundPlayerSpecies=true;_star=starmap[0];_planet=_star[5][0];_panX=_star[0];_panY=_star[1];_panZ=_star[2]-5"/><input type="button" value="Add 10 stars" onclick="interactionCounter+=100"/></div>
<script type="text/javascript">//<![CDATA[
var canvas = document.getElementById( 'c' ),
	ctx = canvas.getContext( '2d' ),
	tmpCanvas = document.getElementById( 'tmpC' ),
	tmpCtx = tmpCanvas.getContext( '2d' ),
	html = document.body.parentNode,
	starmap = [], undefined,
	panX = 0, panY = 0, panZ = 0,
	_panX = 0, _panY = 0, _panZ = 0,
	_star, _planet, state = 0, timer,
	transition_step = 0, threeDGlasses = false,
	starZoom = 16, showVisited = false,
	badges, badgeProgress, playerSpecies,
	colorCache = {}, terrainCache = {},
	getSporeCreaturesCounter = 0, sporeCreatureCache = {},
	loadingSporeCreature = 0, sporeCreatureOverride = false,
	sporeCreatureGetCache = {}, shipHeading = 0,
	shipScale = 1, shipX = 256, shipY = 256,
	_shipX = 256, _shipY = 256, species = [],
	starGenLast = 0, starmapReference = starmap,
	fps = 0, fpsTemp = 0, fpsDiv = document.getElementById( 'fps' ),
	planetsGenerating = 0, planetsGenerated = 0, planetViewDirty = false,
	loc = document.getElementById( 'location' ), justFoundPlayerSpecies = false,
	archetypes = {
		space:    '0x65672ade',
		civ:      '0xccc35c46',
		tribe:    '0x372e2c04',
		creature: '0x9ea3031a'
	}, creatureImg, dialogue_state = 'greet', dialogueStateChange = +new Date,
	arms = 5, armSteps = 64, starSteps = 1, armPartsPerStep = 25, starDist = 512,
	queue = [], interactionCounter = 0;

/**
 * Format description:
 *
 * Species[] species;
 * Star[] starmap;
 * Star = {
 * 	int x;
 * 	int y;
 * 	int z;
 * 	int color;
 * 	int binaryColor;
 * 	Planet[] planets;
 * 	int inhabitant; // Corresponds to the index of a Species in species
 * 	boolean visited;
 * };
 * Planet = {
 * 	int size;
 * 	int speed;
 * 	int r;
 * 	int g;
 * 	int b;
 * 	int posTheta;
 * 	int posR;
 * 	int dist3D;
 * 	String terrainSeed;
 * 	City[] cities;
 * };
 * Species = {
 * 	String id;
 * 	String name;
 * 	String type;
 *  int flags; // see below
 * 	float[] happiness;
 * };
 * City = {
 * 	int size;
 * 	int revenue;
 * 	Date lastUpdate;
 * };
 *
 * Species flags
 * 1 - Reusable
 * 2 - Met
 */

setInterval( function(){
	if ( queue.length ) {
		queue.shift()();
	}
}, 5 );

setInterval( function(){
	if ( state != 0 )
		return;

	starmapReference = [];

	for ( var i = 0; i < starmap.length; i++ ) {
		if ( Math.abs( 256 - getStarX( starmap[i] ) ) < 300 &&
			Math.abs( 256 - getStarY( starmap[i] ) ) < 300 )
			starmapReference.push( starmap[i] );
	}
	starmapReference = starmapReference.sort( function( a, b ){
		if ( a[2] == b[2] )
			return 0;
		return a[2] > b[2] ? 1 : -1;
	} );
}, 100 );

function dbg( /** ... */ ) {
	if ( window.console && console.debug )
		console.debug.apply( console, arguments );
	else
		window.dbg = function(){};
}

if ( !window.localStorage && window.globalStorage && location.hostname )
	window.localStorage = window.globalStorage[location.hostname];

if ( !ctx.fillText && ctx.mozDrawText )
	ctx.fillText = function( text, x, y ){
		canvas.style.font = ctx.font;
		ctx.save();
		ctx.translate( x, y );
		ctx.mozDrawText( text );
		ctx.restore();
	};

function fpsCounter() {
	if ( fpsTemp < ~~( new Date() / 1000 ) ) {
		fpsDiv.innerHTML = fps;
		fps = 1;
		fpsTemp = ~~( new Date() / 1000 );
	} else {
		fps++;
	}
}

function getTravelRadius() {
	return 100 * Math.sqrt( Math.pow( panX, 2 ) + Math.pow( panY, 2 ) ) / 2048 + 50 + document.getElementById( 'badges' ).innerHTML * 25;
}

function rand( x, y ) {
	return ~~( Math.random() * ( y - x + 1 ) + x );
}

function getStarX( star ) {
	if ( !_star )
		_star = [0, 0, 0];
	return 256 + ( ( panX - star[0] ) / ( ( 512 + panZ * 2 - _star[2] - star[2] ) / 128 + 1 ) ) * 10;
}

function getStarY( star ) {
	if ( !_star )
		_star = [0, 0, 0];
	return 256 + ( ( panY - star[1] ) / ( ( 512 + panZ * 2 - _star[2] - star[2] ) / 128 + 1 ) ) * 10;
}

function getStarZ( star ) {
	return Math.max( ( star[2] + 512 - panZ ) / 64, 0 ) + 1;
}

function saveState() {
	if ( !window.localStorage || !localStorage.getItem || !localStorage.setItem || !localStorage.removeItem ) {
		alert( 'Your browser does not support a requirement of SporeSpace\'s saving method. Try Firefox 3.5 or higher.' );
		return;
	}
	localStorage.setItem( 'SporeSpace', JSON.stringify( {
		version: 0.31,
		starmap: starmap,
		species: species,
		state: state,
		pan: [_panX, _panY, _panZ],
		star: starmap.indexOf( _star ),
		planet: _planet && _star && _star[5] ? _star[5].indexOf( _planet ) : -1,
		badges: badges,
		badgeProgress: badgeProgress,
		starGenLast: starGenLast,
		dialogue_state: dialogue_state
	} ) );
}

function resetState() {
	if ( window.localStorage && localStorage.removeItem ) {
		localStorage.removeItem( 'SporeSpace' );
	}
	loadState();
}

function loadState() {
	badges = {
		b10101010: false,
		thousandVisited: false,
		starColors: false,
		binaryColors: false,
		thousandPlanets: false,
		metSpaceEmpire: false,
		populatedStars: false
	};
	badgeProgress = {
		starColors: [],
		binaryColors: [],
		populatedStars: 0
	};

	creatureImg = null;

	if ( window.localStorage && localStorage.getItem && localStorage.getItem( 'SporeSpace' ) ) {
		var tmp = JSON.parse( localStorage.getItem( 'SporeSpace' ) );
		starmap = tmp.starmap;
		state = tmp.state;
		_panX = panX = tmp.pan[0];
		_panY = panY = tmp.pan[1];
		_panZ = panZ = tmp.pan[2];
		_star = starmap[tmp.star];
		_planet = null;
		if ( _star && _star[5] )
			_planet = _star[5][tmp.planet];

		if ( tmp.version >= 0.2 ) {
			badges = tmp.badges;
			badgeProgress = tmp.badgeProgress;
			var badgeCount = 0;
			for ( var i in badges )
				if ( badges[i] )
					badgeCount++;
			document.getElementById( 'badges' ).innerHTML = badgeCount;
		} else {
			document.getElementById( 'badges' ).innerHTML = 0;
		}
		if ( tmp.version >= 0.3 ) {
			species = tmp.species;
			playerSpecies = species[1];
		} else {
			dbg( 'Upgrading to version 0.3' );
			if ( tmp.playerSpecies ) {
				playerSpecies = tmp.playerSpecies;
			} else {
				getSporeCreature( getPlayerSpecies, archetypes.space );
			}
			species = [null, playerSpecies];
			for ( var i = 0; i < starmap.length; i++ ) {
				if ( starmap[i][6] !== undefined ) {
					var index = species.indexOf( starmap[i][6] );
					if ( index == -1 ) {
						species.push( starmap[i][6] );
						index = species.indexOf( starmap[i][6] );
					}
					starmap[i][6] = index;
				}
			}
		}

		if ( tmp.version < 0.31 ) {
			var _species = species.slice( 0, 2 );
			dbg( 'Upgrading to version 0.31' );
			for ( var i = 2, _tmpSpecies; i < species.length; i++ ) {
				if ( !species[i] )
					continue;
				_tmpSpecies = species[i];
				for ( var j = 2; j < _species.length; j++ ) {
					if ( species[i][0] == _species[j][0] &&
						species[i][1] == _species[j][1] &&
						species[i][2] == _species[j][2] ) {
						_tmpSpecies = _species[j];
						break;
					}
				}
				if ( _species.indexOf( _tmpSpecies ) == -1 )
					_species.push( _tmpSpecies );

				for ( var j = 0; j < starmap.length; j++ ) {
					if ( starmap[j][6] == i )
						starmap[j][6] = _species.indexOf( _tmpSpecies );
				}
			}
			species = _species;
		}

		dialogue_state = tmp.dialogue_state || 'greet';

		starGenLast = tmp.starGenLast;

		genAllStars( starGenLast );

		if ( _star )
			getCreatureImage();
	} else {
		starmap = [];
		panX = 0;
		panY = 0;
		panZ = 0;
		_panX = 0;
		_panY = 0;
		_panZ = 0;
		_star = null;
		_planet = null;
		state = 0;
		species = [null, playerSpecies];

		var i = rand( 0, 4 ), j = ( Math.random() * .2 + .6 ) * armSteps,
			x = ( j + 1 ) / armSteps * 4096 * Math.sin( Math.PI * 2 * i / arms + ( j / armSteps * Math.PI * 1.5 ) ),
			y = ( j + 1 ) / armSteps * 4096 * Math.cos( Math.PI * 2 * i / arms + ( j / armSteps * Math.PI * 1.5 ) );
		for ( var k = 0, l = starSteps * ( armSteps - j ) * Math.random(); k < l; k++ ) {
			var a = Math.random() * starDist * ( armSteps - j ) / armSteps,
				b = Math.random() * Math.PI * 2,
				c = Math.random() - .5;
			starmap.push( [~~( x + a * Math.sin( b ) ), ~~( y + a * Math.cos( b ) ), ~~( c * 256 ), rand( 0, 3 ), rand( -1024, 3 )] );
		}


		queue.push( function(){
			for ( var i = 0; i < arms; i++ ) {
				for ( var j = 0; j < armSteps; j += armPartsPerStep ) {
					queue.push( ( function( i, j, stepEnd ){
						return function(){
							for ( ; j < stepEnd; j++ ) {
								var x = ( j + 1 ) / armSteps * 4096 * Math.sin( Math.PI * 2 * i / arms + ( j / armSteps * Math.PI * 1.5 ) ),
									y = ( j + 1 ) / armSteps * 4096 * Math.cos( Math.PI * 2 * i / arms + ( j / armSteps * Math.PI * 1.5 ) );
								for ( var k = 0, l = starSteps * ( armSteps - j ) * Math.random(); k < l; k++ ) {
									var a = Math.random() * starDist * ( armSteps - j ) / armSteps,
										b = Math.random() * Math.PI * 2,
										c = Math.random() - .5;
									starmap.push( [~~( x + a * Math.sin( b ) ), ~~( y + a * Math.cos( b ) ), ~~( c * 256 ), rand( 0, 3 ), rand( -1024, 3 )] );
								}
							}
						};
					} )( i, j, Math.min( armSteps, j + armPartsPerStep ) ) );
				}
			}
		} );

		getSporeCreature( getPlayerSpecies, archetypes.space );

		genAllStars();
	}

	if ( timer )
		clearInterval( timer );

	timer = setInterval( function(){
		[spaceView, spaceToStarView, starView, starToSpaceView, starToPlanetView, planetView, planetToStarView, openDialogueView, dialogueView, closeDialogueView][state]();
		[spaceLocation, noLocation, starLocation, noLocation, noLocation, planetLocation, noLocation, noLocation, starLocation, noLocation][state]();
		fpsCounter();
	}, 25 );
}
loadState();

/**
 * Archetypes:
 * 1 - weak, trader, religious, happy
 * 2 - weak, trader, religious, economic
 * 3 - weak, trader, scientific, happy
 * 4 - weak, trader, scientific, economic
 * 5 - weak, warrior, religious, happy
 * 6 - weak, warrior, religious, economic
 * 7 - weak, warrior, scientific, happy
 * 8 - weak, warrior, scientific, economic
 * 9 - strong, trader, religious, happy
 * 10 - strong, trader, religious, economic
 * 11 - strong, trader, scientific, happy
 * 12 - strong, trader, scientific, economic
 * 13 - strong, warrior, religious, happy
 * 14 - strong, warrior, religious, economic
 * 15 - strong, warrior, scientific, happy
 * 16 - strong, warrior, scientific, economic
 */
function getDialogue( _archetype, _emotion, _state ) {
	var addDebugInfo = function( data ){
			data['Debug: ' + _archetype + ', ' + _emotion + ', ' + _state] = function( _species ){
				return _state;
			};
			return data;
		}, defaultGreetOptions = {
			'I\'m from the ~empire~ empire. I come in peace.': function( _species ){
				_species[4].greeting = 10;
				return 'firstGreetHappy';
			},
			'I\'m from the ~empire~ empire. Prepare to die!': function( _species ){
				_species[4].greeting = -10;
				return 'firstGreetAngry';
			}
		}, defaultOptions = {
			'How would you rate my empire?': function( _species ){
				return 'rateEmpire';
			}
		}, dialogueBackup = {
			greet: {
				text: 'Hello.',
				responses: addDebugInfo( defaultOptions )
			},
			anythingElse: {
				text: 'Anything else?',
				responses: addDebugInfo( defaultOptions )
			},
			rateEmpire: {
				before: function( _species ){
					var ratings = [],
						desc = {
							greeting: 'the way you greeted me',
							strength: 'your strength',
							trust: 'my intuition',
							life: 'my empire\'s way of life'
						};
					for ( var i in _species[4] ) {
						if ( _species[4][i] )
							ratings.push( [i, _species[4][i]] );
					}
					if ( ratings.length )
						this.text += ' For example, ' + desc[ratings[dialogueStateChange % ratings.length][0]] + ' makes me ' + ( ratings[dialogueStateChange % ratings.length][1] > 0 ? 'happy' : 'angry' ) + ' about your empire.';
					return _species;
				},
				text: 'On a scale of 1-5, I give you a ' + ( _emotion + 1 ) + '.',
				responses: { // No debug info - this is not a fallback, it is a basic control.
					'Oh, I see.': function(){
						return 'anythingElse';
					}
				}
			},
			firstGreet: {
				text: 'Friend or foe?',
				responses: addDebugInfo( {
					'I come in peace': function( _species ){
						_species[4] = _species[4] || {};
						_species[4].greeting = 10;
						return 'firstGreetHappy';
					},
					'Who wants to know?': function( _species ){
						_species[4] = _species[4] || {};
						_species[4].greeting = -10;
						return 'firstGreetAngry';
					}
				} )
			},
			firstGreetHappy: {
				text: 'In that case, we welcome you to our home.',
				responses: addDebugInfo( {
					'The feeling is mutual.': function( _species ){
						state = 9;
						transition_step = 0;
					}
				} )
			},
			firstGreetAngry: {
				text: 'If that\'s how you\'re going to act, go away.',
				responses: addDebugInfo( {
					'I choose my own path.': function( _species ){
						state = 9;
						transition_step = 0;
					}
				} )
			}
		}, dialogueTree = [
		[{}, {}, { // Archetype 1 - weak, trader, religious, happy
			firstGreet: {
				before: function( _species ){
					if ( !_species[4] ) {
						_species[4] = {};
						_species[4].strength = 10; // We are amazed by your power
						_species[4].trust = 10; // We like new acquaintances
						_species[4].life = 10; // We enjoy life
					}
				},
				text: 'I hear a voice... Who\'s there?',
				responses: defaultGreetOptions
			}
		}, {}, {}],
		[{}, {}, { // Archetype 2 - weak, trader, religious, economic
			firstGreet: {
				before: function( _species ){
					if ( !_species[4] ) {
						_species[4] = {};
						_species[4].strength = 10; // We are amazed by your power
						_species[4].trust = 10; // We like new acquaintances
						_species[4].life = -10; // Life is tough
					}
					return _species;
				},
				text: 'I hear a voice... Who\'s there?',
				responses: defaultGreetOptions
			}
		}, {}, {}],
		[{}, {}, { // Archetype 3 - weak, trader, scientific, happy
			firstGreet: {
				before: function( _species ){
					if ( !_species[4] ) {
						_species[4] = {};
						_species[4].strength = 10; // We are amazed by your power
						_species[4].trust = 10; // We like new acquaintances
						_species[4].life = 10; // We enjoy life
					}
					return _species;
				},
				text: 'Did I hear a sound? Is there life out there?',
				responses: defaultGreetOptions
			}
		}, {}, {}],
		[{}, {}, { // Archetype 4 - weak, trader, scientific, economic
			firstGreet: {
				before: function( _species ){
					if ( !_species[4] ) {
						_species[4] = {};
						_species[4].strength = 10; // We are amazed by your power
						_species[4].trust = 10; // We like new acquaintances
						_species[4].life = -10; // Life is tough
					}
					return _species;
				},
				text: 'Did I hear a sound? Is there life out there?',
				responses: defaultGreetOptions
			}
		}, {}, {}],
		[{}, {}, { // Archetype 5 - weak, warrior, religious, happy
			firstGreet: {
				before: function( _species ){
					if ( !_species[4] ) {
						_species[4] = {};
						_species[4].strength = 10; // We are amazed by your power
						_species[4].trust = -10; // We distrust strangers
						_species[4].life = 10; // We enjoy life
					}
					return _species;
				},
				text: 'Who are you? State thy name.',
				responses: defaultGreetOptions
			}
		}, {}, {}],
		[{}, {}, { // Archetype 6 - weak, warrior, religious, economic
			firstGreet: {
				before: function( _species ){
					if ( !_species[4] ) {
						_species[4] = {};
						_species[4].strength = 10; // We are amazed by your power
						_species[4].trust = -10; // We distrust strangers
						_species[4].life = -10; // Life is tough
					}
					return _species;
				},
				text: 'Who are you? State thy name.',
				responses: defaultGreetOptions
			},
			firstGreetHappy: {
				text: 'Let the power of Spode be with you.',
				responses: {
					'Thank you.': function( _species ){
						state = 9;
						transition_step = 0;
					}
				}
			}
		}, {}, {}],
		[{}, {}, { // Archetype 7 - weak, warrior, scientific, happy
			firstGreet: {
				before: function( _species ){
					if ( !_species[4] ) {
						_species[4] = {};
						_species[4].strength = 10; // We are amazed by your power
						_species[4].trust = -10; // We distrust strangers
						_species[4].life = 10; // We enjoy life
					}
					return _species;
				},
				text: 'Who are you? State your name.',
				responses: defaultGreetOptions
			}
		}, {}, {}],
		[{}, {}, { // Archetype 8 - weak, warrior, scientific, economic
			firstGreet: {
				before: function( _species ){
					if ( !_species[4] ) {
						_species[4] = {};
						_species[4].strength = 10; // We are amazed by your power
						_species[4].trust = -10; // We distrust strangers
						_species[4].life = -10; // Life is tough
					}
					return _species;
				},
				text: 'Who are you? State your name.',
				responses: defaultGreetOptions
			}
		}, {}, {}],
		[{}, {}, {}, {}, {}], // Archetype 9 - strong, trader, religious, happy
		[{}, {}, {}, {}, {}], // Archetype 10 - strong, trader, religious, economic
		[{}, {}, {}, {}, {}], // Archetype 11 - strong, trader, scientific, happy
		[{}, {}, {}, {}, {}], // Archetype 12 - strong, trader, scientific, economic
		[{}, {}, {
			firstGreetHappy: {
				text: 'You are smart to approach us humbly.',
				responses: {
					'It is an honor.': function( _species ){
						state = 9;
						transition_step = 0;
					}
				}
			}
		}, {}, {}], // Archetype 13 - strong, warrior, religious, happy
		[{}, {}, {
			firstGreetHappy: {
				text: 'You are smart to approach us humbly.',
				responses: {
					'It is an honor.': function( _species ){
						state = 9;
						transition_step = 0;
					}
				}
			}
		}, {}, {}], // Archetype 14 - strong, warrior, religious, economic
		[{}, {}, {
			firstGreetHappy: {
				text: 'You are smart to approach us humbly.',
				responses: {
					'It is an honor.': function( _species ){
						state = 9;
						transition_step = 0;
					}
				}
			}
		}, {}, {}], // Archetype 15 - strong, warrior, scientific, happy
		[{}, {}, {
			firstGreetHappy: {
				text: 'You are smart to approach us humbly.',
				responses: {
					'It is an honor.': function( _species ){
						state = 9;
						transition_step = 0;
					}
				}
			}
		}, {}, {}] // Archetype 16 - strong, warrior, scientific, economic
	], playerDialogue = {
		greet: {
			text: 'Welcome back! How\'s that\nexploring going for you?'
		}
	};
	dialogueTree[-1] = [
		playerDialogue,
		playerDialogue,
		playerDialogue,
		playerDialogue,
		playerDialogue
	];

	function processDialogue( d ) {
		d.text = d.text.replace( /~empire~/g, species[1][1] );

		var responses = {};
		for ( var i in d.responses ) {
			responses[i.replace( /~empire~/g, species[1][1] )] = d.responses[i];
		}
		d.responses = responses;

		return d;
	}

	try {
		return processDialogue( dialogueTree[_archetype][_emotion][_state] );
	} catch ( ex ) {
		try {
			return processDialogue( dialogueBackup[_state] );
		} catch ( ex ) {
			return {
				text: ''
			};
		}
	}
}

function _getSpeciesHappiness( _species ) {
	if ( !_species || !_species[4] )
		return 0;

	var happiness = 0;

	for ( var i in _species[4] ) {
		happiness += _species[4][i];
	}

	return happiness;
}

function getSpeciesHappiness( _species ) {
	return Math.min( Math.max( Math.round( _getSpeciesHappiness( _species ) / 100 ) + 2, 0 ), 4 );
}

function noLocation(){
	loc.innerHTML = '...';
}

function spaceLocation(){
	loc.innerHTML = [panX, panY, panZ].join( ',' );
}

function starLocation(){
	loc.innerHTML = ( _star[6] ? species[_star[6]][1] + ' star' : '<em>Uninhabited</em> star' ) + ' at ' + [panX, panY, panZ].join( ',' ); // todo: Give stars names
}

function planetLocation(){
	loc.innerHTML = ( _star[6] ? species[_star[6]][1] + ' planet' : '<em>Uninhabited</em> planet' ) + ' #' + ( _star[5].indexOf( _planet ) + 1 ); // todo: Give stars and planets names
}

function cleanCreatureData( data ) {
	return [
		data.id.split( /\// )[1],
		data.title,
		data.modeltype[0]
	];
}

function getPlayerSpecies( _playerSpecies ) {
	playerSpecies = species[_playerSpecies];

	delete species[_playerSpecies];

	if ( !playerSpecies[3] )
		playerSpecies[3] = 0;

	playerSpecies[3] |= 2;

	_star = starmap[0];
	while ( !_star[5] || _star[5].length < 5 ) {
		generatePlanets( _star );
	}
	species[1] = playerSpecies;
	_star[6] = 1;
	_panX = _star[0];
	_panY = _star[1];
	_panZ = _star[2] - 5;
	_planet = _star[5][0];
	justFoundPlayerSpecies = true;
}

function getCreatureImage() {
	creatureImg = new Image();
	if ( _star[6] && species[_star[6]] && species[_star[6]][2] == archetypes.space ) {
		if ( species[_star[6]][0].substr( 0, 3 ) == '000' ) {
			creatureImg = document.getElementById( 'creatureImg' );
			var _ctx = creatureImg.getContext( '2d' );

			_ctx.clearRect( 0, 0, 310, 310 );

			_ctx.fillStyle = 'rgb( ' + getCreatureColor( species[_star[6]] ).join( ', ' ) + ' )';
			_ctx.save();

			// Converted from http://www.google.com/images?q=alien+svg&imgtype=i_similar&ei=lBnmS9_aGY6wNpzg_YYI&oi=image_sil&tbnid=alsu7TxWxSS8xM:
			_ctx.translate( 50, 5 );
			_ctx.moveTo( 174.69269,291.15004 );
			_ctx.bezierCurveTo( 171.21711,290.30057, 162.7743,286.74669, 155.9309,283.25253 );
			_ctx.lineTo( 143.48835,276.89951 );
			_ctx.lineTo( 131.25014,282.79896 );
			_ctx.bezierCurveTo( 116.74633,289.79056, 109.73358,291.77454, 99.524344,291.77454 );
			_ctx.bezierCurveTo( 89.144804,291.77454, 87.511934,290.55334, 87.511934,282.79064 );
			_ctx.bezierCurveTo( 87.511934,277.22517, 87.706784,276.75895, 91.261934,273.81772 );
			_ctx.bezierCurveTo( 95.944084,269.94411, 105.03172,267.07504, 116.86491,265.73455 );
			_ctx.bezierCurveTo( 121.881,265.16632, 126.25255,264.38036, 126.57948,263.98797 );
			_ctx.bezierCurveTo( 126.9064,263.59559, 126.36444,252.28421, 125.37512,238.85157 );
			_ctx.bezierCurveTo( 123.48649,213.20839, 123.63416,213.93446, 116.10511,193.27454 );
			_ctx.bezierCurveTo( 113.93514,187.32006, 113.55987,184.63158, 113.59264,175.27454 );
			_ctx.bezierCurveTo( 113.61382,169.22454, 114.05403,162.02454, 114.57087,159.27454 );
			_ctx.bezierCurveTo( 115.08771,156.52454, 115.7866,151.79954, 116.12397,148.77454 );
			_ctx.bezierCurveTo( 116.90346,141.7852, 115.61542,140.48645, 105.28353,137.844 );
			_ctx.bezierCurveTo( 95.072534,135.23247, 88.781924,131.87597, 83.636314,126.29364 );
			_ctx.bezierCurveTo( 79.656194,121.97573, 78.072404,119.0832, 71.191644,103.56564 );
			_ctx.bezierCurveTo( 69.007634,98.640246, 67.934584,97.473686, 63.373014,95.065639 );
			_ctx.bezierCurveTo( 55.194044,90.747986, 51.818894,87.004252, 50.963654,81.301134 );
			_ctx.bezierCurveTo( 50.272174,76.690011, 50.392684,76.319429, 53.382944,73.86141 );
			_ctx.bezierCurveTo( 55.113804,72.438629, 56.525914,70.712036, 56.520954,70.024536 );
			_ctx.bezierCurveTo( 56.515724,69.30031, 57.773794,68.774536, 59.511934,68.774536 );
			_ctx.bezierCurveTo( 61.161934,68.774536, 62.511934,68.527653, 62.511934,68.225906 );
			_ctx.bezierCurveTo( 62.511934,67.92416, 59.624074,63.655476, 56.094474,58.739942 );
			_ctx.bezierCurveTo( 47.478054,46.740209, 46.416414,41.774536, 52.467364,41.774536 );
			_ctx.bezierCurveTo( 56.494634,41.774536, 60.796894,47.088888, 63.523054,55.431037 );
			_ctx.bezierCurveTo( 64.753074,59.194961, 66.153764,62.702036, 66.635694,63.224536 );
			_ctx.bezierCurveTo( 67.142634,63.774144, 67.518154,60.191687, 67.526674,54.724536 );
			_ctx.bezierCurveTo( 67.541774,45.050235, 68.554294,41.381283, 71.761934,39.377807 );
			_ctx.bezierCurveTo( 74.261104,37.816841, 76.940334,39.768979, 77.309994,43.420224 );
			_ctx.bezierCurveTo( 77.473924,45.039419, 76.681024,50.526026, 75.547984,55.612685 );
			_ctx.bezierCurveTo( 73.299004,65.709325, 73.552884,67.180654, 78.117924,70.506052 );
			_ctx.lineTo( 80.754894,72.426955 );
			_ctx.lineTo( 79.133414,78.004653 );
			_ctx.bezierCurveTo( 76.844484,85.878298, 77.107654,90.700621, 80.222344,97.958626 );
			_ctx.bezierCurveTo( 86.686684,113.02212, 93.236414,121.3919, 101.38247,124.99875 );
			_ctx.bezierCurveTo( 106.41677,127.2278, 119.82033,128.50207, 126.01283,127.34035 );
			_ctx.bezierCurveTo( 129.21775,126.7391, 137.55548,120.61139, 137.48614,118.90818 );
			_ctx.bezierCurveTo( 137.47195,118.55968, 135.70906,117.46771, 133.56861,116.48158 );
			_ctx.bezierCurveTo( 125.17552,112.61481, 109.03936,94.300358, 101.22743,79.774536 );
			_ctx.bezierCurveTo( 94.970114,68.139424, 93.244424,61.102429, 93.667164,48.945263 );
			_ctx.bezierCurveTo( 94.008704,39.122931, 94.325514,37.678235, 97.651844,30.774536 );
			_ctx.bezierCurveTo( 102.12138,21.498108, 107.16276,15.602935, 114.90601,10.598233 );
			_ctx.bezierCurveTo( 123.13162,5.2817609, 131.81861,2.768821, 141.86728,2.7989818 );
			_ctx.bezierCurveTo( 151.45291,2.8277528, 156.85767,3.9333509, 164.05303,7.3373008 );
			_ctx.bezierCurveTo( 189.87402,19.552583, 198.82269,49.402661, 184.92768,76.968867 );
			_ctx.bezierCurveTo( 177.6871,91.333403, 163.54203,108.3722, 153.40417,114.94132 );
			_ctx.bezierCurveTo( 148.79878,117.92551, 148.68662,118.11149, 150.14767,120.34134 );
			_ctx.bezierCurveTo( 152.72784,124.27918, 157.71873,127.40373, 165.3591,129.86448 );
			_ctx.bezierCurveTo( 173.40761,132.45668, 175.07846,134.06373, 178.34727,142.35677 );
			_ctx.bezierCurveTo( 182.84905,153.77787, 183.76532,177.77594, 180.14036,189.31978 );
			_ctx.bezierCurveTo( 178.0796,195.88237, 173.0532,203.40135, 168.91235,206.11574 );
			_ctx.bezierCurveTo( 166.50863,207.69142, 165.32423,209.46291, 164.50847,212.70259 );
			_ctx.bezierCurveTo( 163.414,217.04912, 160.53549,251.81645, 160.51987,260.8778 );
			_ctx.lineTo( 160.51194,265.48107 );
			_ctx.lineTo( 168.0957,266.12213 );
			_ctx.bezierCurveTo( 181.14187,267.22494, 192.79817,271.06077, 197.55343,275.81603 );
			_ctx.bezierCurveTo( 200.15824,278.42084, 200.36872,288.81859, 197.8588,290.90164 );
			_ctx.bezierCurveTo( 195.4888,292.86856, 182.30245,293.00995, 174.69269,291.15004 );
			_ctx.closePath();
			_ctx.fill();

			var color = getCreatureColor( species[_star[6]] );
			color[0] = Math.min( color[0] + 64, 255 );
			color[1] = Math.min( color[1] + 64, 255 );
			color[2] = Math.min( color[2] + 64, 255 );
			_ctx.fillStyle = 'rgb( ' + color.join( ', ' ) + ' )';
			_ctx.beginPath();
			_ctx.moveTo( 133.88124,76.094337 );
			_ctx.bezierCurveTo( 133.57052,59.170384, 120.89754,45.902894, 104.11475,44.931444 );
			_ctx.lineTo( 97.217574,44.53221 );
			_ctx.lineTo( 97.874914,48.643021 );
			_ctx.bezierCurveTo( 100.37314,64.266082, 109.91608,75.806569, 125.00079,81.446937 );
			_ctx.bezierCurveTo( 133.72924,84.710618, 134.03603,84.525702, 133.88124,76.094337 );
			_ctx.closePath();
			_ctx.moveTo( 159.22443,81.632519 );
			_ctx.bezierCurveTo( 173.57846,76.319947, 181.4644,67.809604, 185.90163,52.843035 );
			_ctx.bezierCurveTo( 188.40984,44.382952, 188.48244,44.494005, 180.73017,44.932047 );
			_ctx.bezierCurveTo( 163.82036,45.887537, 151.81251,58.332355, 150.72942,76.024536 );
			_ctx.bezierCurveTo( 150.17866,85.021103, 150.1392,84.995055, 159.22443,81.632519 );
			_ctx.closePath();
			_ctx.fill();
			_ctx.restore();
		} else
			creatureImg.src = 'http://www.spore.com/static/thumb/' + species[_star[6]][0].substr( 0, 3 ) + '/' + species[_star[6]][0].substr( 3, 3 ) + '/' + species[_star[6]][0].substr( 6, 3 ) + '/' + species[_star[6]][0] + '.png';
	} else
		creatureImg.src = 'data:image/gif,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAQAIBRAA7';
}

function gotBadge( name ) {
	var hudCenter = document.getElementById( 'hudCenter' ),
		fadeTimer,
		opacity = 0,
		names = {
			b10101010: '10101010',
			thousandVisited: 'KiloStar',
			starColors: 'Rainbow of Stars',
			binaryColors: 'Binary Rainbow',
			thousandPlanets: 'Thousands of Rocks',
			metSpaceEmpire: 'We\'re Not Alone',
			populatedStars: 'First Fifty Friends'
		},
		descriptions = {
			b10101010: 'Find a binary system with ten planets',
			thousandVisited: 'Visit 1000 stars',
			starColors: 'Visit each of the four star colors',
			binaryColors: 'Visit each of the sixteen binary star combinations',
			thousandPlanets: 'Visit 1000 planets in a single session',
			metSpaceEmpire: 'Contact another space-faring empire',
			populatedStars: 'Visit 50 stars that are inhabited by a space empire'
		};

	badges[name] = true;

	hudCenter.style.display = '';
	hudCenter.style.opacity = 0;
	fadeTimer = setInterval( function(){
		hudCenter.style.opacity = ( opacity += 1 ) / 25;
		hudCenter.innerHTML = '<strong>Badge unlocked:</strong><br/>' + names[name].substr( 0, ~~( 1 + opacity / 25 * names[name].length ) ) + '<br/><small>' + descriptions[name] + '</small>';
		if ( opacity >= 25 ) {
			clearInterval( fadeTimer );
			document.getElementById( 'badges' ).innerHTML++;
			setTimeout( function(){
				fadeTimer = setInterval( function(){
					hudCenter.style.opacity = 2 - ( opacity += 1 ) / 25;
					hudCenter.innerHTML = '<strong>Badge unlocked:</strong><br/>' + names[name].substr( ~~( ( opacity - 25 ) / 25 * names[name].length ) ) + '<br/><small>' + descriptions[name] + '</small>';
					if ( opacity >= 50 ) {
						clearInterval( fadeTimer );
						hudCenter.style.display = 'none';
					}
				}, 25 );
			}, 1500 );
		}
	}, 25 );
}

function drawCircle( x, y, r, d, color, strokeOnly ) {
	// Skip drawing things that are off the canvas
	if ( strokeOnly )
		r += strokeOnly;
	if ( r == 0 ||
		x + r + d < 0 || x - r - d > 512 ||
		y + r + d < 0 || y - r - d > 512 )
		return;
	if ( strokeOnly )
		r -= strokeOnly;

	if ( strokeOnly )
		ctx.lineWidth = strokeOnly;

	if ( threeDGlasses ) {
		ctx.globalCompositeOperation = 'lighter';
		if ( strokeOnly )
			ctx.strokeStyle = 'rgb( ' + color[0] + ', 0, 0 )';
		else
			ctx.fillStyle = 'rgb( ' + color[0] + ', 0, 0 )';
		ctx.beginPath();
		ctx.arc( x + ( d - 4 ), y, r, 0, Math.PI * 2, false );
		if ( strokeOnly )
			ctx.stroke();
		else
			ctx.fill();
		if ( strokeOnly )
			ctx.strokeStyle = 'rgb( 0, ' + color[1] + ', ' + color[2] + ' )';
		else
			ctx.fillStyle = 'rgb( 0, ' + color[1] + ', ' + color[2] + ' )';
		ctx.beginPath();
		ctx.arc( x - ( d - 4 ), y, r, 0, Math.PI * 2, false );
		if ( strokeOnly )
			ctx.stroke();
		else
			ctx.fill();
		ctx.globalCompositeOperation = 'source-over';
	} else {
		if ( strokeOnly )
			ctx.strokeStyle = 'rgb( ' + color[0] + ', ' + color[1] + ', ' + color[2] + ' )';
		else
			ctx.fillStyle = 'rgb( ' + color[0] + ', ' + color[1] + ', ' + color[2] + ' )';
		ctx.beginPath();
		ctx.arc( x, y, r, 0, Math.PI * 2, false );
		if ( strokeOnly )
			ctx.stroke();
		else
			ctx.fill();
	}
}

function getCreatureColor( creature ) {
	if ( !creature || !creature[0] ) {
		console.log( creature );
		return [0, 0, 0];
	}

	if ( colorCache[creature[0]] )
		return colorCache[creature[0]];

	var tmp = sha1( creature[0] );
	colorCache[creature[0]] = [
		Math.min( parseInt( tmp.substr( 0, 2 ), 16 ) + 64, 256 ),
		Math.min( parseInt( tmp.substr( 2, 2 ), 16 ) + 64, 256 ),
		Math.min( parseInt( tmp.substr( 4, 2 ), 16 ) + 64, 256 )
	];
	return colorCache[creature[0]];
}

function drawStarMap() {
	var d = +new Date;
	ctx.fillStyle = '#000';
	ctx.fillRect( 0, 0, 512, 512 );

	drawCircle( getStarX( [0, 0, 0] ), getStarY( [0, 0, 0] ), getStarZ( [0, 0, 1024] ), 0, [255, 0, 255] );

	drawCircle( 256, 256, Math.abs( getStarY( [panX, panY + getTravelRadius(), panZ] ) - 256 ), 0, [255, 255, 255], 1 );

	for ( var i = 0; i < starmap.length; i++ ) {
		if ( !starmap[i][6] ||
			!species[starmap[i][6]] ||
			species[starmap[i][6]][2] != archetypes.space ||
			!species[starmap[i][6]][3] ||
			!( species[starmap[i][6]][3] & 2 ) )
			continue;
		for ( var j = i + 1; j < starmap.length; j++ ) {
			if ( starmap[i][6] == starmap[j][6] ) {
				ctx.strokeStyle = 'rgb( ' + getCreatureColor( species[starmap[i][6]] ).join( ', ' ) + ' )';
				ctx.lineWidth = 1;
				ctx.beginPath();
				ctx.moveTo( getStarX( starmap[i] ), getStarY( starmap[i] ) );
				ctx.lineTo( getStarX( starmap[j] ), getStarY( starmap[j] ) );
				ctx.stroke();
			}
		}
	}
	for ( var i = 0; i < starmapReference.length; i++ ) {
		if ( starmapReference[i][4] >= 0 ) {
			if ( starmapReference[i][6] && species[starmapReference[i][6]][2] == archetypes.space && ( species[starmapReference[i][6]][3] & 2 || ( Math.abs( starmapReference[i][0] - panX ) < 50 && Math.abs( starmapReference[i][1] - panY ) < 50 && Math.abs( starmapReference[i][2] - panZ ) < 50 ) ) ) {
				drawCircle( getStarX( starmapReference[i] ) - Math.sin( d / 2500 + i / 5 ) * getStarZ( starmapReference[i] ), getStarY( starmapReference[i] ) - Math.cos( d / 3000 + i / 5 ) * getStarZ( starmapReference[i] ), getStarZ( starmapReference[i] ) + 2, getStarZ( starmapReference[i] ), getCreatureColor( species[starmapReference[i][6]] ), starmapReference[i][6] == 1 ? 5 : 2 );
				drawCircle( getStarX( starmapReference[i] ) - Math.sin( d / 2500 + i / 5 + Math.PI ) * getStarZ( starmapReference[i] ), getStarY( starmapReference[i] ) - Math.cos( d / 3000 + i / 5 + Math.PI ) * getStarZ( starmapReference[i] ) + 2, getStarZ( starmapReference[i] ), getStarZ( starmapReference[i] ), getCreatureColor( species[starmapReference[i][6]] ), starmapReference[i][6] == 1 ? 5 : 2  );
			}
			drawCircle( getStarX( starmapReference[i] ) - Math.sin( d / 2500 + i / 5 ) * getStarZ( starmapReference[i] ), getStarY( starmapReference[i] ) - Math.cos( d / 3000 + i / 5 ) * getStarZ( starmapReference[i] ), getStarZ( starmapReference[i] ), getStarZ( starmapReference[i] ), [[255, 255, 255], [255, 255, 0], [255, 0, 0], [0, 0, 255]][starmapReference[i][3]] );
			drawCircle( getStarX( starmapReference[i] ) - Math.sin( d / 2500 + i / 5 + Math.PI ) * getStarZ( starmapReference[i] ), getStarY( starmapReference[i] ) - Math.cos( d / 3000 + i / 5 + Math.PI ) * getStarZ( starmapReference[i] ), getStarZ( starmapReference[i] ), getStarZ( starmapReference[i] ), [[255, 255, 255], [255, 255, 0], [255, 0, 0], [0, 0, 255]][starmapReference[i][4]] );
		} else {
			if ( starmapReference[i][6] && species[starmapReference[i][6]][2] == archetypes.space && ( species[starmapReference[i][6]][3] & 2 || ( Math.abs( starmapReference[i][0] - panX ) < 50 && Math.abs( starmapReference[i][1] - panY ) < 50 && Math.abs( starmapReference[i][2] - panZ ) < 50 ) ) )
				drawCircle( getStarX( starmapReference[i] ), getStarY( starmapReference[i] ), getStarZ( starmapReference[i] ) + 2, getStarZ( starmapReference[i] ), getCreatureColor( species[starmapReference[i][6]] ), starmapReference[i][6] == 1 ? 5 : 2 );
			drawCircle( getStarX( starmapReference[i] ), getStarY( starmapReference[i] ), getStarZ( starmapReference[i] ), getStarZ( starmapReference[i] ), [[255, 255, 255], [255, 255, 0], [255, 0, 0], [0, 0, 255]][starmapReference[i][3]] );
		}
		if ( threeDGlasses ) {
			if ( showVisited && starmapReference[i][6] ) {
				ctx.globalCompositeOperation = 'lighter';
				ctx.strokeStyle = '#f00';
				ctx.lineWidth = 1;
				ctx.strokeRect( getStarX( starmapReference[i] ) + ( getStarZ( starmapReference[i] ) - 4 ) - getStarZ( starmapReference[i] ) * 1.25, getStarY( starmapReference[i] ) - getStarZ( starmapReference[i] ) * 1.25, getStarZ( starmapReference[i] ) * 2.5, getStarZ( starmapReference[i] ) * 2.5 );
				ctx.strokeStyle = '#0ff';
				ctx.strokeRect( getStarX( starmapReference[i] ) - ( getStarZ( starmapReference[i] ) - 4 ) - getStarZ( starmapReference[i] ) * 1.25, getStarY( starmapReference[i] ) - getStarZ( starmapReference[i] ) * 1.25, getStarZ( starmapReference[i] ) * 2.5, getStarZ( starmapReference[i] ) * 2.5 );
				ctx.globalCompositeOperation = 'source-over';
			}
		} else {
			if ( showVisited && starmapReference[i][6] ) {
				ctx.strokeStyle = '#fff';
				ctx.strokeRect( getStarX( starmapReference[i] ) - getStarZ( starmapReference[i] ) * 1.25, getStarY( starmapReference[i] ) - getStarZ( starmapReference[i] ) * 1.25, getStarZ( starmapReference[i] ) * 2.5, getStarZ( starmapReference[i] ) * 2.5 );
			}
		}
	}

	drawShip();
}
drawStarMap();

function drawShip( x, y, rot, scale, sp ) {
	ctx.save();
	ctx.translate( x || shipX, y || shipY );
	ctx.rotate( rot || shipHeading );
	ctx.fillStyle = ( sp && species[sp] ) || playerSpecies ? 'rgb( ' + getCreatureColor( sp && species[sp] ? species[sp] : playerSpecies ).join( ', ' ) + ' )' : '#0a0';
	ctx.strokeStyle = '#000';
	ctx.lineWidth = scale || shipScale;
	ctx.beginPath();
	ctx.moveTo( 8 * ctx.lineWidth, 16 * ctx.lineWidth );
	ctx.lineTo( -8 * ctx.lineWidth, 16 * ctx.lineWidth );
	ctx.lineTo( 0, -16 * ctx.lineWidth );
	ctx.closePath();
	ctx.fill();
	ctx.beginPath();
	ctx.moveTo( 8 * ctx.lineWidth, 16 * ctx.lineWidth );
	ctx.lineTo( -8 * ctx.lineWidth, 16 * ctx.lineWidth );
	ctx.lineTo( 0, -16 * ctx.lineWidth );
	ctx.closePath();
	ctx.stroke();
	ctx.restore();
}

function angleSmooth( a, b ) {
	if ( a < b - Math.PI )
		a += Math.PI * 2;
	else if ( b < a - Math.PI )
		b += Math.PI * 2;

	var c = ( a + a + b ) / 3;

	if ( c >= Math.PI * 2 )
		c -= Math.PI * 2;

	return c;
}

function spaceView() {
	var change = false;

	if ( panX > _panX ) {
		panX = Math.floor( ( panX + _panX ) / 2 );
		change = true;
	}
	if ( panX < _panX ) {
		panX = Math.ceil( ( panX + _panX ) / 2 );
		change = true;
	}

	if ( panY > _panY ) {
		panY = Math.floor( ( panY + _panY ) / 2 );
		change = true;
	}
	if ( panY < _panY ) {
		panY = Math.ceil( ( panY + _panY ) / 2 );
		change = true;
	}

	if ( panZ > _panZ ) {
		panZ = Math.floor( ( panZ + _panZ ) / 2 );
		change = true;
	}
	if ( panZ < _panZ ) {
		panZ = Math.ceil( ( panZ + _panZ ) / 2 );
		change = true;
	}

	shipScale = 1;
	shipX = 256;
	shipY = 256;
	if ( change ) {
		shipHeading = angleSmooth( shipHeading, Math.atan2( getStarX( [_panX, _panY, _panZ] ) - 256, 256 - getStarY( [_panX, _panY, _panZ] ) ) );
	} else {
		shipHeading = angleSmooth( shipHeading, 0 );
	}

	if ( _star && !change && panZ < _star[2] - 4 ) {
		state = 1;
		transition_step = 0;
	}

	drawStarMap();
}

/**
 * Generate data for all stars with an index >= i.
 * This waits half a second between each star.
 */
function genAllStars( i ) {
	i = i || 1;

	if ( i >= starmap.length ) {
		dbg( 'Done generating stars' );
		return;
	}

	starGenLast = i;

	dbg( 'Generating star #%d/%d', i, starmap.length - 1 );

	if ( !starmap[i][5] ) {
		generatePlanets( starmap[i] );
	}

	var seed, seedChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012346789`~!@#$%^&*()-_=+[]{}|:;,./?';
	for ( var j = 0; j < starmap[i][5].length; j++ ) {
		if ( !starmap[i][5][j][8] ) {
			seed = '';
			for ( var k = 0, l = rand( 16, 32 ); k < l; k++ ) {
				seed += seedChars.charAt( rand( 0, seedChars.length - 1 ) );
			}
			starmap[i][5][j][8] = seed;
		}
	}

	if ( starmap[i][6] === undefined ) {
		if ( !starmap[i][5].length ) {
			starmap[i][6] = 0;
			setTimeout( genAllStars, 500, i + 1 );
		} else {
			var r = rand( 0, 474 ), setStarInhabitants = ( function( star ){
				return function( inhabitants ) {
					star[6] = inhabitants;

					if ( species[inhabitants][2] == archetypes.space || species[inhabitants][2] == archetypes.civ ) { // Only space and civ have cities
						for ( var j = 0; j <= Math.sqrt( star[5].length - 1 ); j++ ) {
							if ( !star[5][j][9] ) {
								star[5][j][9] = [];
								for ( var k = 0, l = rand( 8, 16 ); k < l; k++ ) {
									star[5][j][9].push( [rand( 2, 8 ), 0, +new Date()] );
								}
							}
						}
					}

					setTimeout( genAllStars, 500, i + 1 );
				}
			} )( starmap[i] );
			if ( r < 304 ) { // Uninhabited
				starmap[i][6] = 0;
				setTimeout( genAllStars, 500, i + 1 );
			} else if ( r < 314 ) // Civilization
				getSporeCreature( setStarInhabitants, archetypes.civ );
			else if ( r < 364 ) // Creature
				getSporeCreature( setStarInhabitants, archetypes.creature );
			else if ( r < 464 ) // Space
				getSporeCreature( setStarInhabitants, archetypes.space, 5, starmap[i].slice( 0, 3 ) );
			else // Tribe
				getSporeCreature( setStarInhabitants, archetypes.tribe );
		}
	} else {
		setTimeout( genAllStars, 500, i + 1 );
	}
}

function spaceToStarView() {
	ctx.fillStyle = ['#fff', '#ff0', '#f00', '#00f'][_star[3]];
	if ( _star[4] >= 0 && transition_step % 2 == 0 )
		ctx.fillStyle = ['#fff', '#ff0', '#f00', '#00f'][_star[4]];

	ctx.beginPath();
	ctx.moveTo( 256, 256 );
	ctx.arc( 256, 256, transition_step * 16, Math.PI / 8 * ( transition_step % 16 ), Math.PI / 8 * ( transition_step % 16 + 1.01 ), false );
	ctx.lineTo( 256, 256 );
	ctx.fill();
	transition_step++;
	if ( transition_step > 40 ) {
		if ( !_star[5] ) {
			generatePlanets( _star );
			if ( !badges.b10101010 && _star[4] >= 0 && _star[5].length == 10 ) {
				gotBadge( 'b10101010' );
			}
		}
		var seed, seedChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012346789`~!@#$%^&*()-_=+[]{}|:;,./?';
		for ( var i = 0; i < _star[5].length; i++ ) {
			if ( !_star[5][i][8] ) {
				seed = '';
				for ( var j = 0, k = rand( 16, 32 ); j < k; j++ ) {
					seed += seedChars.charAt( rand( 0, seedChars.length - 1 ) );
				}
				_star[5][i][8] = seed;
			}
		}

		if ( _star[6] === undefined ) {
			if ( !_star[5].length ) {
				_star[6] = 0;
			} else {
				var r = rand( 0, 474 ), star = _star;
				function setStarInhabitants( inhabitants ) { star[6] = inhabitants; }
				if ( r < 304 ) // Uninhabited
					_star[6] = 0;
				else if ( r < 314 ) // Civilization
					getSporeCreature( setStarInhabitants, archetypes.civ );
				else if ( r < 414 ) // Creature
					getSporeCreature( setStarInhabitants, archetypes.creature );
				else if ( r < 464 ) // Space
					getSporeCreature( setStarInhabitants, archetypes.space, 5, _star );
				else // Tribe
					getSporeCreature( setStarInhabitants, archetypes.tribe );
			}
		}


		if ( !_star[7] ) {
			_star[7] = true;

			if ( !badges.starColors && _star[4] < 0 ) {
				badgeProgress.starColors[_star[3]] = true;

				var starColors = true;
				for ( var i = 0; i < 4; i++ ) {
					if ( !badgeProgress.starColors[i] ) {
						starColors = false;
						break;
					}
				}
				if ( starColors )
					gotBadge( 'starColors' );
			}
			if ( !badges.binaryColors && _star[4] >= 0 ) {
				badgeProgress.binaryColors[_star[3] + _star[4] * 4] = true;
				var binaryColors = true;
				for ( var i = 0; i < 16; i++ ) {
					if ( !badgeProgress.binaryColors[i] ) {
						binaryColors = false;
						break;
					}
				}
				if ( binaryColors )
					gotBadge( 'binaryColors' );
			}
			if ( !badges.thousandVisited ) {
				var numberVisited = 0;
				for ( var i = 0; i < starmap.length; i++ ) {
					if ( starmap[i][7] )
						numberVisited++;
				}
				if ( numberVisited >= 1000 ) {
					gotBadge( 'thousandVisited' );
				}
			}
			if ( !badges.populatedStars ) {
				if ( _star[6] && _star[6][2] == archetypes.space ) {
					if ( !badgeProgress.populatedStars )
						badgeProgress.populatedStars = 0;
					badgeProgress.populatedStars++;
					if ( badgeProgress.populatedStars >= 50 )
						gotBadge( 'populatedStars' );
				}
			}
		}

		getCreatureImage();

		if ( justFoundPlayerSpecies ) {
			justFoundPlayerSpecies = false;
			transition_step = 0;
			state = 4;
		} else {
			state = 2;
		}
		starZoom = 16;
	}
}

function generatePlanets( star ) {
	star[5] = [];
	for ( var i = 0, j = rand( 0, 10 ); i < j; i++ ) {
		star[5].push( [rand( 1, 16 ), rand( 64, 128 ), rand( 0, 255 ), rand( 0, 255 ), rand( 0, 255 ), rand( 0, 3599 ), rand( 0, 16 ), rand( 0, 8 )] );
	}
}

function starView( x, y, angle, skipContactPane ) {
	if ( starZoom == 8 ) {
		transition_step = 0;
		state = 3;
		return;
	}
	var d = +new Date;
	ctx.fillStyle = '#000';
	ctx.fillRect( 0, 0, 512, 512 );
	if ( _star[4] >= 0 ) {
		if ( _star[6] && species[_star[6]][2] == archetypes.space ) {
			drawCircle( 256 + Math.sin( d / 10000 ) * starZoom * 4, 256 + Math.cos( d / 10000 ) * starZoom * 4, [5, 3, 2, 4][_star[3]] * starZoom + 8, 8, getCreatureColor( species[_star[6]] ), 8 );
			drawCircle( 256 + Math.sin( d / 10000 + Math.PI ) * starZoom * 4, 256 + Math.cos( d / 10000 + Math.PI ) * starZoom * 4, [5, 3, 2, 4][_star[4]] * starZoom + 8, 8, getCreatureColor( species[_star[6]] ), 8 );
		}
		drawCircle( 256 + Math.sin( d / 10000 ) * starZoom * 4, 256 + Math.cos( d / 10000 ) * starZoom * 4, [5, 3, 2, 4][_star[3]] * starZoom, 5, [[255, 255, 255], [255, 255, 0], [255, 0, 0], [0, 0, 255]][_star[3]] );
		drawCircle( 256 + Math.sin( d / 10000 + Math.PI ) * starZoom * 4, 256 + Math.cos( d / 10000 + Math.PI ) * starZoom * 4, [5, 3, 2, 4][_star[4]] * starZoom, 5, [[255, 255, 255], [255, 255, 0], [255, 0, 0], [0, 0, 255]][_star[4]] );
	} else {
		if ( _star[6] && species[_star[6]][2] == archetypes.space )
			drawCircle( 256, 256, [5, 3, 2, 4][_star[3]] * starZoom + 8, 8, getCreatureColor( species[_star[6]] ), 8 );
		drawCircle( 256, 256, [5, 3, 2, 4][_star[3]] * starZoom, 5, [[255, 255, 255], [255, 255, 0], [255, 0, 0], [0, 0, 255]][_star[3]] );
	}
	for ( var i = 0; i < _star[5].length; i++ ) {
		var planetX = 256 + Math.sin( d / 2500 / _star[5][i][0] + _star[5][i][5] / 1800 * Math.PI ) * _star[5][i][1] * starZoom / 8,
			planetY = 256 + Math.cos( d / 2500 / _star[5][i][0] + _star[5][i][5] / 1800 * Math.PI ) * _star[5][i][1] * starZoom / 8,
			planetSize = ( 4 + _star[5][i][6] / 4 ) * starZoom / 4;
		drawCircle( planetX, planetY, planetSize, _star[5][i][7], [_star[5][i][2], _star[5][i][3], _star[5][i][4]] );
		if ( _star[5][i][9] ) {
			ctx.fillStyle = 'rgba( 0, 0, 0, .25 )';
			for ( var j = 0; j < _star[5][i][9].length; j++ ) {
				ctx.beginPath();
				ctx.arc( planetX + Math.sin( j ) * planetSize / Math.sqrt( j + 1 ), planetY + Math.cos( j ) * planetSize / Math.pow( j + 1, 1 / 3 ), _star[5][i][9][j][0] * planetSize / 32, Math.PI, 0, false );
				ctx.closePath();
				ctx.fill();
			}
		}
	}

	shipScale = starZoom / 8;
	shipX = x || 256;
	shipY = y || 256;
	shipHeading = angleSmooth( shipHeading, angle || 0 );

	drawShip();

	if ( !skipContactPane && _star[6] && species[_star[6]] && species[_star[6]][2] == archetypes.space ) {
		ctx.fillStyle = 'rgba( 0, 0, 0, .75 )';
		ctx.beginPath();
		ctx.moveTo( 512, 400 );
		ctx.arc( 315, 415, 15, Math.PI * 3 / 2, Math.PI, true );
		ctx.lineTo( 300, 512 );
		ctx.lineTo( 512, 512 );
		ctx.fill();
		ctx.strokeStyle = 'rgb( ' + getCreatureColor( species[_star[6]] ).join( ', ' ) + ' )';
		ctx.lineWidth = 2;
		ctx.beginPath();
		ctx.moveTo( 512, 400 );
		ctx.arc( 315, 415, 15, Math.PI * 3 / 2, Math.PI, true );
		ctx.lineTo( 300, 512 );
		ctx.stroke();
		if ( creatureImg && ( creatureImg.complete || creatureImg.getContext ) && species[_star[6]][3] & 2 ) // If the image is loaded and the species has been met, display the image.
			ctx.drawImage( creatureImg, 300, 400, 112, 112 );
		else { // Otherwise, draw a question mark
			ctx.fillStyle = '#fff';
			ctx.font = '100px sans-serif';
			ctx.fillText( '?', 320, 500 );
		}
		ctx.fillStyle = '#fff';
		ctx.font = '18px sans-serif';
		ctx.fillText( 'Contact', 425, 425 );
		ctx.fillStyle = ['#f00', '#f70', '#ff0', '#00f', '#0f0'][getSpeciesHappiness( species[_star[6]] )];
		ctx.font = '50px sans-serif';
		ctx.fillText( _getSpeciesHappiness( species[_star[6]] ) >= 0 ? '+' + _getSpeciesHappiness( species[_star[6]] ) : _getSpeciesHappiness( species[_star[6]] ), 400, 475 );
	}
}

function starToSpaceView() {
	switch ( transition_step ) {
		case 0:
			ctx.fillStyle = ['#fff', '#ff0', '#f00', '#00f'][_star[3]];
			ctx.fillRect( 0, 0, 512, 512 );
			_panX = _star[0];
			_panY = _star[1];
			_panZ = _star[2];
			panZ = -1024;
		case 1:
		case 2:
			transition_step++;
			return;
	}
	state = 0;
}

function starToPlanetView() {
	var d = +new Date,
		x = Math.sin( d / 2500 / _planet[0] + _planet[5] / 1800 * Math.PI ) * _planet[1] * starZoom / 8,
		y = Math.cos( d / 2500 / _planet[0] + _planet[5] / 1800 * Math.PI ) * _planet[1] * starZoom / 8;
	x = ( _shipX * ( 50 - transition_step ) + ( 256 + x ) * transition_step ) / 50;
	y = ( _shipY * ( 50 - transition_step ) + ( 256 + y ) * transition_step ) / 50;
	starView( x, y, Math.atan2( x - shipX, shipY - y ) );

	transition_step++;

	if ( transition_step > 50 ) {
		state = 5;
		planetViewDirty = true;
		if ( !genTerrain( _planet[8], [_planet[2], _planet[3], _planet[4]] )[2] )
			document.getElementById( 'hudPlanetLoading' ).style.display = '';
	}
}

function planetView() {
	if ( !planetViewDirty )
		return;

	var terrain = genTerrain( _planet[8], [_planet[2], _planet[3], _planet[4]] );
	ctx.putImageData( terrain[0], 0, 0 );

	if ( terrain[2] ) {
		document.getElementById( 'hudPlanetLoading' ).style.display = 'none';

		if ( !_planet[9] ) {
			if ( _star[6] )
				_planet[9] = [];

			if ( _star[6] && ( species[_star[6]][2] == archetypes.space || species[_star[6]][2] == archetypes.civ ) ) { // Only space and civ have cities
				if ( _star[5].indexOf( _planet ) <= Math.sqrt( _star[5].length - 1 ) ) {
					for ( var i = 0, j = rand( 8, 16 ); i < j; i++ ) {
						_planet[9].push( [rand( 2, 8 ), 0, +new Date()] );
					}
				}
			}
		}

		for ( var i = 0; i < _planet[9].length; i++ ) {
			ctx.fillStyle = 'rgba( 200, 200, 255, .6 )';
			ctx.beginPath();
			ctx.arc( terrain[2][i][1], terrain[2][i][2], _planet[9][i][0] * 3, 0, Math.PI, true );
			ctx.closePath();
			ctx.fill();

			ctx.strokeStyle = '#000';
			ctx.lineWidth = 1;
			ctx.beginPath();
			ctx.arc( terrain[2][i][1], terrain[2][i][2], _planet[9][i][0] * 3, 0, Math.PI, true );
			ctx.closePath();
			ctx.stroke();

			ctx.fillStyle = '#fff';
			ctx.beginPath();
			ctx.arc( terrain[2][i][1] - _planet[9][i][0], terrain[2][i][2] - _planet[9][i][0] * 1.5, Math.min( _planet[9][i][0] / 2, 3 ), 0, Math.PI * 2, false );
			ctx.fill();
		}

		planetViewDirty = false;
	}
}

function planetToStarView() {
	var x = ( _shipX * ( 50 - transition_step ) + 256 * transition_step ) / 50,
		y = ( _shipY * ( 50 - transition_step ) + 256 * transition_step ) / 50;
	starView( x, y, Math.atan2( x - shipX, shipY - y ) );

	transition_step++;

	if ( transition_step > 50 ) {
		state = 2;
		document.getElementById( 'hudPlanetLoading' ).style.display = 'none';
	}
}

function openDialogueView() {
	transition_step++;

	starView( 256, 256, 0, true );

	ctx.strokeStyle = 'rgb( ' + getCreatureColor( species[_star[6]] ).join( ', ' ) + ' )';
	ctx.lineWidth = 5;
	ctx.fillStyle = 'rgba( 0, 0, 0, .75 )';
	ctx.beginPath();
	ctx.arc( 20, 20 + Math.pow( 32 * ( 40 - transition_step ) / 40, 2 ), 15, Math.PI, Math.PI * 3 / 2, false );
	ctx.arc( 492, 20 + Math.pow( 32 * ( 40 - transition_step ) / 40, 2 ), 15, Math.PI * 3 / 2, 0, false );
	ctx.arc( 492, 492 + Math.pow( 32 * ( 40 - transition_step ) / 40, 2 ), 15, 0, Math.PI / 2, false );
	ctx.arc( 20, 492 + Math.pow( 32 * ( 40 - transition_step ) / 40, 2 ), 15, Math.PI / 2, Math.PI, false );
	ctx.closePath();
	ctx.fill();
	ctx.stroke();

	if ( transition_step >= 40 ) {
		state = 8;
		dialogue_state = 'greet';
		dialogueStateChange = +new Date;
		if ( !( species[_star[6]][3] & 2 ) ) {
			species[_star[6]][3] |= 2;
			dialogue_state = 'firstGreet';
			if ( !badges.metSpaceEmpire ) {
				gotBadge( 'metSpaceEmpire' );
			}
		}
	}
}

function dialogueView() {
	starView( 256, 256, 0, true );

	// Frame
	ctx.strokeStyle = 'rgb( ' + getCreatureColor( species[_star[6]] ).join( ', ' ) + ' )';
	ctx.lineWidth = 5;
	ctx.fillStyle = 'rgba( 0, 0, 0, .75 )';
	ctx.beginPath();
	ctx.arc( 20, 20, 15, Math.PI, Math.PI * 3 / 2, false );
	ctx.arc( 492, 20, 15, Math.PI * 3 / 2, 0, false );
	ctx.arc( 492, 492, 15, 0, Math.PI / 2, false );
	ctx.arc( 20, 492, 15, Math.PI / 2, Math.PI, false );
	ctx.closePath();
	ctx.fill();
	ctx.stroke();

	// Close button
	ctx.fillStyle = '#fff';
	ctx.font = '18px sans-serif';
	ctx.fillText( 'X', 490, 25 );

	// The creature you're talking to
	if ( creatureImg && ( creatureImg.complete || creatureImg.getContext ) ) {
		ctx.save();
		ctx.scale( -1, 1 );
		ctx.drawImage( creatureImg, -150, 20, 128, 128 );
		ctx.restore();
	} else { // Otherwise, draw a question mark
		ctx.fillStyle = '#fff';
		ctx.font = '110px sans-serif';
		ctx.fillText( '?', 30, 105 );
	}

	var dialogue = getDialogue( _star[6] == 1 ? -1 : parseInt( sha1( species[_star[6]][0] ).charAt( 6 ), 16 ), getSpeciesHappiness( species[_star[6]] ), dialogue_state );

	if ( dialogue.before )
		dialogue.before( species[_star[6]] );

	ctx.fillStyle = '#fff';
	ctx.font = '24px sans-serif';

	for ( var text = wordwrap( dialogue.text, 30 ).split( /\n/ ), i = 0; i < text.length; i++ ) {
		ctx.fillText( text[i].replace( /^\s+|\s+$/g, '' ), 150, 50 + 30 * i );
	}

	ctx.font = '18px sans-serif';
	var top = 130;
	if ( dialogue.responses )
		for ( var i in dialogue.responses ) {
			for ( var text = wordwrap( i, 50 ).split( /\n/ ), j = 0; j < text.length; j++ ) {
				ctx.fillText( text[j].replace( /^\s+|\s+$/g, '' ), 30, top += 20 );
			}
			top += 10;
		}
}

function closeDialogueView() {
	transition_step++;

	starView( 256, 256, 0, true );

	ctx.strokeStyle = 'rgb( ' + getCreatureColor( species[_star[6]] ).join( ', ' ) + ' )';
	ctx.lineWidth = 5;
	ctx.fillStyle = 'rgba( 0, 0, 0, .75 )';
	ctx.beginPath();
	ctx.arc( 20, 20 + Math.pow( 32 * transition_step / 40, 2 ), 15, Math.PI, Math.PI * 3 / 2, false );
	ctx.arc( 492, 20 + Math.pow( 32 * transition_step / 40, 2 ), 15, Math.PI * 3 / 2, 0, false );
	ctx.arc( 492, 492 + Math.pow( 32 * transition_step / 40, 2 ), 15, 0, Math.PI / 2, false );
	ctx.arc( 20, 492 + Math.pow( 32 * transition_step / 40, 2 ), 15, Math.PI / 2, Math.PI, false );
	ctx.closePath();
	ctx.fill();
	ctx.stroke();

	if ( transition_step >= 40 ) {
		state = 2;
	}
}

canvas.addEventListener( 'click', function( e ){
	var star, d = +new Date,
		x = e.clientX - canvas.offsetLeft + document.body.scrollLeft + html.scrollLeft,
		y = e.clientY - canvas.offsetTop + document.body.scrollTop + html.scrollTop,
		handled = false;

	interactionCounter++;

	dbg( '(%d, %d)', x, y );
	if ( state == 0 ) {
		for ( var i = 0; i < starmapReference.length; i++ ) {
			if ( starmapReference[i][4] >= 0 ) {
				if ( Math.sqrt( Math.pow( getStarX( starmapReference[i] ) - Math.sin( d / 5000 + i / 5 ) * getStarZ( starmapReference[i] ) - x, 2 ) + Math.pow( getStarY( starmapReference[i] ) - Math.cos( d / 6000 + i / 5 ) * getStarZ( starmapReference[i] ) - y, 2 ) ) <= getStarZ( starmapReference[i] ) + 1 ||
					Math.sqrt( Math.pow( getStarX( starmapReference[i] ) - Math.sin( d / 5000 + i / 5 + Math.PI ) * getStarZ( starmapReference[i] ) - x, 2 ) + Math.pow( getStarY( starmapReference[i] ) - Math.cos( d / 6000 + i / 5 + Math.PI ) * getStarZ( starmapReference[i] ) - y, 2 ) ) <= getStarZ( starmapReference[i] ) + 1 )
					star = starmapReference[i];
			} else {
				if ( Math.sqrt( Math.pow( getStarX( starmapReference[i] ) - x, 2 ) + Math.pow( getStarY( starmapReference[i] ) - y, 2 ) ) <= getStarZ( starmapReference[i] ) + 1 )
					star = starmapReference[i];
			}
		}
		if ( star && Math.sqrt( Math.pow( star[0] - panX, 2 ) + Math.pow( star[1] - panY, 2 ) ) <= getTravelRadius() ) {
			_star = star;
			_panX = star[0];
			_panY = star[1];
			_panZ = star[2];
			handled = true;
		}
	}

	if ( state == 2 && !handled ) {
		if ( _star[6] && species[_star[6]] &&
			species[_star[6]][2] == archetypes.space && 
			x >= 425 && y >= 410 &&
			x <= 490 && y <= 425 ) {
			state = 7;
			transition_step = 0;
			dialogueStateChange = +new Date;
			dialogue_state = 'greet';
			handled = true;
		}
	}

	if ( ( state == 2 || state == 4 || state == 6 ) && !handled ) {
		for ( var i = 0; i < _star[5].length; i++ ) {
			if ( Math.sqrt( Math.pow( 256 + Math.sin( d / 2500 / _star[5][i][0] + _star[5][i][5] / 1800 * Math.PI ) * _star[5][i][1] * starZoom / 8 - x, 2 ) + Math.pow( 256 + Math.cos( d / 2500 / _star[5][i][0] + _star[5][i][5] / 1800 * Math.PI ) * _star[5][i][1] * starZoom / 8 - y, 2 ) ) <= ( 4 + _star[5][i][6] / 4 ) * starZoom / 4 ) {
				_planet = _star[5][i];
				state = 4;
				transition_step = 0;
				_shipX = shipX;
				_shipY = shipY;
				handled = true;
				break;
			}
		}
	}

	if ( state == 8 && !handled ) {
		if ( x >= 490 && y >= 10 &&
			x <= 500 && y <= 25 ) {
			state = 9;
			transition_step = 0;
			handled = true;
		}
		var dialogue = getDialogue( _star[6] == 1 ? -1 : parseInt( sha1( species[_star[6]][0] ).charAt( 6 ), 16 ), getSpeciesHappiness( species[_star[6]] ), dialogue_state );
		if ( y >= 130 && dialogue.responses && !handled ) {
			var top = 130;
			for ( var i in dialogue.responses ) {
				for ( var text = wordwrap( i, 50 ).split( /\n/ ), j = 0; !handled && j < text.length; j++ ) {
					if ( y >= top && y <= ( top += 20 ) ) {
						dbg( dialogue_state = dialogue.responses[i]( species[_star[6]] ) );
						dialogueStateChange = +new Date;
						handled = true;
					}
				}
				top += 10;
			}
		}
	}
}, false );

setInterval( function(){
	for ( var starCount = Math.sqrt( interactionCounter ); starCount > 0; starCount-- ) {
		var i = rand( 0, 4 ), j = Math.random() * armSteps,
			x = ( j + 1 ) / armSteps * 4096 * Math.sin( Math.PI * 2 * i / arms + ( j / armSteps * Math.PI * 1.5 ) ),
			y = ( j + 1 ) / armSteps * 4096 * Math.cos( Math.PI * 2 * i / arms + ( j / armSteps * Math.PI * 1.5 ) );
		for ( var k = 0, l = starSteps * ( armSteps - j ) * Math.random(); k < l; k++ ) {
			var a = Math.random() * starDist * ( armSteps - j ) / armSteps,
				b = Math.random() * Math.PI * 2,
				c = Math.random() - .5;
			starmap.push( [~~( x + a * Math.sin( b ) ), ~~( y + a * Math.cos( b ) ), ~~( c * 256 ), rand( 0, 3 ), rand( -1024, 3 )] );
		}
		genAllStars( starmap.length - 1 );
	}
	interactionCounter = 0;
}, 5000 );

function handleWheel( delta ) {
	if ( state == 0 ) {
		if ( delta > 0 && _star && _panZ < _star[2] - 4 )
			return;
		if ( _panZ - delta * 30 > 10000 )
			return;
		_panZ -= ~~( delta * 30 );
	} else if ( state == 2 ) {
		starZoom = Math.min( Math.max( starZoom + ~~( delta * 3 ), 8 ), 24 );
	} else if ( state == 5 ) {
		if ( delta < 0 ) {
				document.getElementById( 'hudPlanetLoading' ).style.display = 'none';
			transition_step = 0;
			state = 6;
			var d = +new Date;
			_shipX = 256 + Math.sin( d / 2500 / _planet[0] + _planet[5] / 1800 * Math.PI ) * _planet[1] * starZoom / 8;
			_shipY = 256 + Math.cos( d / 2500 / _planet[0] + _planet[5] / 1800 * Math.PI ) * _planet[1] * starZoom / 8;
		}
	}
}

function wheel( event ) {
	var delta = 0;
	if (!event) event = window.event;
	if (event.wheelDelta) {
		delta = event.wheelDelta/120; 
		if (window.opera) delta = -delta;
	} else if (event.detail) {
		delta = -event.detail/3;
	}
	event.preventDefault();
	if (delta)
		handleWheel(delta);
}

window.addEventListener( 'DOMMouseScroll', wheel, false );
window.addEventListener( 'mousewheel', wheel, false );

window.addEventListener( 'keydown', function( e ) {
	if ( e.keyCode == 67 && e.shiftKey && e.ctrlKey ) {
		document.getElementById( 'cheat' ).style.display = document.getElementById( 'cheat' ).style.display ? '' : 'none';
	} else if ( e.keyCode == 107 || e.keyCode == 38 ) {
		handleWheel( 1 );
	} else if ( e.keyCode == 109 || e.keyCode == 40 ) {
		handleWheel( -1 );
	} else {
		return;
	}
	e.preventDefault();
}, false );

window.addEventListener( 'error', function( e ){
	if ( loadingSporeCreature ) {
		sporeCreatureOverride = true;
		loadingSporeCreature = 0;

		for ( var i = 0; i <= getSporeCreaturesCounter; i++ ) {
			if ( sporeCreatureGetCache[i] ) {
				getSporeCreature.apply( window, sporeCreatureGetCache[i] );
				delete sporeCreatureGetCache[i];
			}
		}
	}
}, false );

function genRandCreature( assetType ) {
	var name = '', nameParts = [
		// _ is for a random letter (non-vowel), - for a vowel.
		// ( and ) for capital letters and vowels.
		// All replacements remain constant (if - is an a, -- will be aa)
		['(uh', '(ah', '(o', 'T-f-', '(ill-', ')r--dl'], // First part (always used)
		['saur', '-saur', 'up-gus', '-cerus'] // Last part (always used)
	], consonants = 'bcdfghjklmnprstvwxyz', vowels = 'aeiou';
	function expand( a ) {
		return a.replace(
			/_/g, consonants.charAt( rand( 0, consonants.length - 1 ) ) ).replace(
			/-/g, vowels.charAt( rand( 0, vowels.length - 1 ) ) ).replace(
			/\(/g, consonants.charAt( rand( 0, consonants.length - 1 ) ).toUpperCase() ).replace(
			/\)/g, vowels.charAt( rand( 0, vowels.length - 1 ) ).toUpperCase() );
	}


	name += nameParts[0][rand( 0, nameParts[0].length - 1 )];
	name += nameParts[1][rand( 0, nameParts[1].length - 1 )];

	name = expand( name );

	return ['000' + sha1( name + ':' + assetType + rand( 0, 99999999 ) ).substr( 0, 9 ), name, assetType];
}

function getSporeCreature( callback, assetType, allowReuse, reuseOrigin ) {
	if ( sporeCreatureCache[assetType] && sporeCreatureCache[assetType].length ) {
		var tmp = sporeCreatureCache[assetType].shift();
		if ( allowReuse && !rand( 0, allowReuse ) ) {
			dbg( 'Attempting to use pre-existing creature...' );

			function calcOriginDistance( a, b ) {
				return Math.sqrt( Math.pow( a[0] - b[0], 2 ) + Math.pow( a[1] - b[1], 2 ) + Math.pow( a[2] - b[2], 2 ) );
			}
			var _starmap = starmap.slice( 0 );
			_starmap = _starmap.sort( function( a, b ) {
				if ( reuseOrigin )
					return calcOriginDistance( a, reuseOrigin ) - calcOriginDistance( b, reuseOrigin );
				return rand( -1, 1 );
			} );
			for ( var i = 0; i < _starmap.length; i++ ) {
				if ( _starmap[i][6] && species[_starmap[i][6]] && species[_starmap[i][6]][3] && species[_starmap[i][6]][3] & 1 && Math.sqrt( Math.pow( _starmap[i][0] - reuseOrigin[0], 2 ) + Math.pow( _starmap[i][1] - reuseOrigin[1], 2 ) ) <= getTravelRadius() ) {
					dbg( 'Used pre-existing creature #%d', _starmap[i][6] );
					callback( _starmap[i][6] );
					sporeCreatureCache[assetType].unshift( tmp );
					return;
				}
			}

			// We couldn't find a pre-existing one, so let's just use a new one.
			dbg( 'Could not find a pre-existing creature' );
		}

		tmp[3] = 0;
		if ( allowReuse )
			tmp[3] |= 1;

		species.push( tmp );
		callback( species.indexOf( tmp ) );
		return;
	}
	if ( sporeCreatureOverride ) {
		// Generate a random creature, add it to the list.
		if ( !sporeCreatureCache[assetType] )
			sporeCreatureCache[assetType] = [];
		sporeCreatureCache[assetType].push( genRandCreature( assetType ) );
		getSporeCreature( callback, assetType, allowReuse, reuseOrigin );
		return;
	} else {
		var s = document.createElement( 'script' );
		s.type = 'text/javascript';
		s.addEventListener( 'load', function( e ){
			this.parentNode.removeChild( this );
			loadingSporeCreature--;
		}, false );

		window['gotSporeCreatures' + getSporeCreaturesCounter] = ( function( funcID ){
			sporeCreatureGetCache[funcID] = [callback, assetType, allowReuse, reuseOrigin];
			return function( data ) {
				delete window['gotSporeCreatures' + funcID];
				delete sporeCreatureGetCache[funcID];

				for ( var i = 0; i < data.query.count; i++ ) {
					var entry = cleanCreatureData( data.query.results.entry[i] );
					if ( !sporeCreatureCache[entry[2]] )
						sporeCreatureCache[entry[2]] = [entry];
					else
						sporeCreatureCache[entry[2]].push( entry );
				}

				getSporeCreature( callback, assetType, allowReuse, reuseOrigin );
			}
		} )(getSporeCreaturesCounter);

		s.src = 'http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20atom%20where%20url%20%3D%20\'http%3A%2F%2Fwww.spore.com%2Fatom%2Fassets%2Fview%2FRANDOM%2F0%2F20%2FCREATURE%3F' + Math.random() + '\'&format=json&callback=gotSporeCreatures' + getSporeCreaturesCounter;
		document.body.appendChild( s );
		loadingSporeCreature++;

		getSporeCreaturesCounter++;
	}
}

function genTerrain( seed, color ) {
	// noise() and fbm() taken from http://lonelypinkelephants.com/random/perlin.html

	/* This [noise function][1] (with quintic derivatives) gratefully borrowed from
	 * [Inigo Quilez][2]. It was optimsfucated with Google's [Closure Compiler][3].
	 *
	 * [1]: http://gamedev.net/community/forums/topic.asp?topic_id=479101
	 * [2]: http://iquilezles.org/
	 * [3]: http://code.google.com/closure/compiler/ */
	function noise( a, b, magic ) {
		var e = a | 0,
			c = b | 0;

		a -= e;
		b -= c;

		var g = a * a * a * ( a * ( a * 6 - 15 ) + 10 ),
			h = b * b * b * ( b * ( b * 6 - 15 ) + 10 ),
			d = c * 53 + e;

		e = magic[d & 255];

		var f = magic[d + 1 & 255];

		c = magic[d + 53 & 255];
		d = e - f - c + magic[d + 54 & 255];
		f = f - e;
		c = c - e;

		return {noise: e + f * g + c * h + d * g * h,
				dx: 30 * a * a * ( a * (a - 2) + 1) * (f + d * h),
				dy: 30 * b * b * ( b * (b - 2) + 1) * (c + d * g)};
	}

	/* This [Perlin Noise][1] function is also thanks to Inigo Quilez (following a
	 * similar algorithm used in [Elevated][2]).
	 *
	 * [1]: http://iquilezles.org/www/articles/morenoise/morenoise.htm
	 * [2]: http://www.pouet.net/prod.php?which=52938 */
	function fbm( x, y, magic ) {
		var dx = 0,
			dy = 0,
			value = 0,
			k = 0.5,
			i, n;

		for ( i = 0; i < 5; i++ ) {
			n = noise( x, y, magic );
			dx += n.dx;
			dy += n.dy;
			value += k * n.noise / ( 1 + dx * dx + dy * dy );

			k *= 0.5;
			x = x * 2 + 17;
			y = y * 2 + 71;
		}

		return {noise: value, dx: dx, dy: dy};
	}

	if ( color[0] + color[1] + color[2] < 384 ) {
		for ( var i = 0; i < 3; i++ )
			color[i] = Math.min( color[i] + 64, 255 );
	}

	if ( terrainCache[seed + ',' + color.join( ',' )] )
		return terrainCache[seed + ',' + color.join( ',' )];

	var data = '', magic = [],
		imagedata = tmpCtx.getImageData( 0, 0, 512, 512 ),
		depthVector = [], cities = [];

	planetsGenerating++;

	for ( var i = 0; i < 13; i++ ) {
		data = sha1( data + seed );
		for ( var j = 0; j < 20; j++ ) {
			magic.push( parseInt( data.substr( j * 2, 2 ), 16 ) / 128 - 1 );
		}
	}

	function searchCircle( x, y ) {
		var type = depthVector[y][x],
			r = 0,
			_x, _y;

		while ( true ) {
			for ( var i = 0; i <= r; i++ ) {
				_x = x + i;
				_y = y + ~~Math.sqrt( r * r - i * i );
				if ( _x < 0 || _x > 511 ||
					_y < 0 || _y > 511 ||
					type != depthVector[_y][_x] )
					return r;
				_x = x + x - _x;
				if ( _x < 0 || _x > 511 ||
					_y < 0 || _y > 511 ||
					type != depthVector[_y][_x] )
					return r;
				_y = y + y - _y;
				if ( _x < 0 || _x > 511 ||
					_y < 0 || _y > 511 ||
					type != depthVector[_y][_x] )
					return r;
				_x = x + x - _x;
				if ( _x < 0 || _x > 511 ||
					_y < 0 || _y > 511 ||
					type != depthVector[_y][_x] )
					return r;
			}
			r++;
		}
	}

	var i = 0, y = 0;
	queue.push( function buildStep1(){
		var tmpDepthVector = [];
		for (var x = 0; x < 512; x++ ) {
			var c = ( fbm( x / 64, y / 64, magic ).noise + 1 ) / 2;

			tmpDepthVector.push( ~~( c * 4 ) );

			imagedata.data[i++] = ~~( c * color[0] );
			imagedata.data[i++] = ~~( c * color[1] );
			imagedata.data[i++] = ~~( c * color[2] );
			imagedata.data[i++] = 255;
		}
		depthVector.push( tmpDepthVector );
		terrainCache[seed + ',' + color.join( ',' )] = [imagedata, depthVector];
		y++;

		if ( y >= 512 ) {
			y = 0;
			queue.push( function buildStep2(){
				for ( var x = 0; x < 512; x += 16 ) {
					cities.push( [searchCircle( x, y ), x, y] );
					if ( cities.length > 300 ) { // Reduce memory usage
						cities = cities.sort( function( a, b ){
							return Math.max( Math.min( b[0] - a[0], 1 ), -1 );
						} ).slice( 0, 300 );
					}
				}
				y += 16;
				if ( y >= 512 ) {
					cities = cities.sort( function( a, b ){
						return Math.max( Math.min( b[0] - a[0], 1 ), -1 );
					} );
					var _cities = cities;
					cities = [];

					for ( var i = 0, j; i < _cities.length; i++ ) {
						for ( j = 0; j < cities.length; j++ ) {
							if ( Math.sqrt( Math.pow( _cities[i][1] - cities[j][1], 2 ) + Math.pow( _cities[i][2] - cities[j][2], 2 ) ) <= _cities[i][0] + cities[j][0] )
								break;
						}
						if ( j == cities.length )
							cities.push( _cities[i] );
					}
					_cities = null;
					cities = cities.slice( 0, 100 );

					terrainCache[seed + ',' + color.join( ',' )] = [imagedata, depthVector, cities];

					planetsGenerating--;
					planetsGenerated++;
					if ( planetsGenerated == 1000 && !badges.thousandPlanets )
						gotBadge( 'thousandPlanets' );
				} else {
					_planet && _planet[8] == seed ? queue.unshift( buildStep2 ) : queue.push( buildStep2 );
				}
			}, 5 );
		} else {
			_planet && _planet[8] == seed ? queue.unshift( buildStep1 ) : queue.push( buildStep1 );
		}
	} );

	terrainCache[seed + ',' + color.join( ',' )] = [imagedata, depthVector];

	return [imagedata, depthVector];
}

// From phpjs.org
function sha1(e){function k(o,m){return o<<m|o>>>32-m}function n(o){var m="",p,v;for(p=7;p>=0;p--){v=o>>>p*4&15;m+=v.toString(16)}return m}var b,d,h=new Array(80),q=1732584193,r=4023233417,s=2562383102,t=271733878,u=3285377520,c,f,g,i,j;c=e.length;var l=[];for(b=0;b<c-3;b+=4){d=e.charCodeAt(b)<<24|e.charCodeAt(b+1)<<16|e.charCodeAt(b+2)<<8|e.charCodeAt(b+3);l.push(d)}switch(c%4){case 0:b=2147483648;break;case 1:b=e.charCodeAt(c-1)<<24|8388608;break;case 2:b=e.charCodeAt(c-2)<<24|e.charCodeAt(c- 1)<<16|32768;break;case 3:b=e.charCodeAt(c-3)<<24|e.charCodeAt(c-2)<<16|e.charCodeAt(c-1)<<8|128;break}for(l.push(b);l.length%16!=14;)l.push(0);l.push(c>>>29);l.push(c<<3&4294967295);for(e=0;e<l.length;e+=16){for(b=0;b<16;b++)h[b]=l[e+b];for(b=16;b<=79;b++)h[b]=k(h[b-3]^h[b-8]^h[b-14]^h[b-16],1);d=q;c=r;f=s;g=t;i=u;for(b=0;b<=19;b++){j=k(d,5)+(c&f|~c&g)+i+h[b]+1518500249&4294967295;i=g;g=f;f=k(c,30);c=d;d=j}for(b=20;b<=39;b++){j=k(d,5)+(c^f^g)+i+h[b]+1859775393&4294967295;i=g;g=f;f=k(c,30);c=d;d= j}for(b=40;b<=59;b++){j=k(d,5)+(c&f|c&g|f&g)+i+h[b]+2400959708&4294967295;i=g;g=f;f=k(c,30);c=d;d=j}for(b=60;b<=79;b++){j=k(d,5)+(c^f^g)+i+h[b]+3395469782&4294967295;i=g;g=f;f=k(c,30);c=d;d=j}q=q+d&4294967295;r=r+c&4294967295;s=s+f&4294967295;t=t+g&4294967295;u=u+i&4294967295}j=n(q)+n(r)+n(s)+n(t)+n(u);return j.toLowerCase()}
function wordwrap(f){var c=arguments.length>=2?arguments[1]:75,i=arguments.length>=3?arguments[2]:"\n",g=arguments.length>=4?arguments[3]:false,d,a,h,b,e;f+="";if(c<1)return f;d=-1;for(h=(e=f.split(/\r\n|\n|\r/)).length;++d<h;e[d]+=b){b=e[d];for(e[d]="";b.length>c;e[d]+=b.slice(0,a)+((b=b.slice(a)).length?i:""))a=g==2||(a=b.slice(0,c+1).match(/\S*(\s)?$/))[1]?c:a.input.length-a[0].length||g==1&&c||a.input.length+(a=b.slice(c).match(/^\S*/)).input.length}return e.join("\n")};
//]]></script>
<p id="copyright"><a href="http://forum.spore.com/jforum/posts/list/0/55717.page">SporeSpace</a> copyright 2010 <a href="http://llamaslayers.net/">LlamaSlayers</a>.<br/><a href="http://www.spore.com/">Spore</a> copyright 2008 <a href="http://www.ea.com/">Electronic Arts Inc.</a><br/>Trademarks belong to their respective owners.</p></body></html>

